package com.ryan.ohos.extension;

import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.utils.Rect;

import java.util.HashMap;

/**
 * 偏移component的帮助类，由于{@link Component}内的{@link Component#setPosition(int, int)}、{@link Component#setTop(int)}
 * 等能整体偏移所有component的api在滚动中会有莫名其妙的问题，因此通过循环为所有component设置
 * {@link Component#setComponentPosition(Rect)}来模拟实现该api
 */
public class ComponentPositionHelper {
    private static final HashMap<Integer, ComponentInfo> container = new HashMap<>();

    public static void reset() {
        container.clear();
    }

    /**
     * 通过setComponentPosition设置component的偏移量，位于该控件下方的控件也会偏移
     * @param component 控件
     * @param offset 偏移量
     */
    public static void offsetTopAndBottom(Component component, int offset) {
        ComponentInfo info = container.get(component.hashCode());
        if (offset == 0 && info == null) {
            return;
        }

        saveComponentInfo(component, true);

        int delta;
        if (info != null) {
            delta = offset - info.offsetTop;
        } else {
            delta = offset;
        }

        ComponentContainer parent = (ComponentContainer) component.getComponentParent();
        int count = parent.getChildCount();
        for (int i = 0; i < count; i++) {
            Component child = parent.getComponentAt(i);
            ComponentInfo componentInfo = container.get(child.hashCode());
            if (child != component) {
                if (componentInfo == null) continue;
                Rect layoutRect = componentInfo.layoutRect;
                if (layoutRect.top > getTop(component)) {
                    setNewPositionY(child, offset, delta, componentInfo);
                }
            } else {
                setNewPositionY(child, offset, delta, componentInfo);
            }
        }
    }

    /**
     * 通过setComponentPosition设置component的偏移量，位于该控件右方的控件也会偏移
     * @param component 控件
     * @param offset 偏移量
     */
    public static void offsetLeftAndRight(Component component, int offset) {
        ComponentInfo info = container.get(component.hashCode());
        if (offset == 0 && info == null) {
            return;
        }

        if (info != null && info.offsetLeft == offset) {
            return;
        }

        saveComponentInfo(component, false);

        int delta;
        if (info != null) {
            delta = offset - info.offsetLeft;
        } else {
            delta = offset;
        }

        ComponentContainer parent = (ComponentContainer) component.getComponentParent();
        int count = parent.getChildCount();
        for (int i = 0; i < count; i++) {
            Component child = parent.getComponentAt(i);
            ComponentInfo componentInfo = container.get(child.hashCode());
            if (child != component) {
                if (componentInfo == null) continue;
                Rect layoutRect = componentInfo.layoutRect;
                if (layoutRect.left > getLeft(component)) {
                    setNewPositionX(child, offset, delta, componentInfo);
                }
            } else {
                setNewPositionX(child, offset, delta, componentInfo);
            }
        }
    }

    /**
     * 获取控件的当前的top偏移
     * @param component 需要获取偏移的控件
     * @return top偏移
     */
    public static int getCurrentOffsetTop(Component component) {
        ComponentInfo info = container.get(component.hashCode());
        if (info == null) {
            return 0;
        }
        return info.offsetTop;
    }

    /**
     * 获取控件的当前的left偏移
     * @param component 需要获取偏移的控件
     * @return left偏移
     */
    public static int getCurrentOffsetLeft(Component component) {
        if (component == null) {
            return 0;
        }
        ComponentInfo info = container.get(component.hashCode());
        if (info == null) {
            return 0;
        }
        return info.offsetLeft;
    }

    private static void setNewPositionY(Component component, int offset, int delta, ComponentInfo info) {
        Rect rect = component.getComponentPosition();
        int lastTop = rect.top;
        rect.top = delta + info.offsetTop + info.layoutTop + info.otherOffsetTop;
        if (lastTop == rect.top) {
            return;
        }
        rect.bottom = rect.top + component.getHeight();
        component.setComponentPosition(rect);

        if (info.targetTop) {
            info.offsetTop = offset;
        } else {
            info.otherOffsetTop += delta;
        }
    }

    private static void setNewPositionX(Component component, int offset, int delta, ComponentInfo info) {
        Rect rect = component.getComponentPosition();
        int lastLeft = rect.left;
        rect.left = delta + info.offsetLeft + info.layoutLeft + info.otherOffsetLeft;
        if (lastLeft == rect.left) {
            return;
        }
        rect.right = rect.left + component.getWidth();
        component.setComponentPosition(rect);

        if (info.targetLeft) {
            info.offsetLeft = offset;
        } else {
            info.otherOffsetLeft += delta;
        }
    }

    private static int getTop(Component component) {
        return container.get(component.hashCode()).layoutRect.top;
    }

    private static int getLeft(Component component) {
        return container.get(component.hashCode()).layoutRect.left;
    }

    private static int getLayoutTop(Component component) {
        return component.getComponentPosition().top;
    }

    private static int getLayoutLeft(Component component) {
        return component.getComponentPosition().left;
    }

    private static void saveComponentInfo(Component component, boolean fromTop) {
        ComponentContainer parent = (ComponentContainer) component.getComponentParent();
        int count = parent.getChildCount();
        for (int i = 0; i < count; i++) {
            Component child = parent.getComponentAt(i);
            ComponentInfo info = container.get(child.hashCode());
            if (info == null) {
                info = new ComponentInfo();
                info.layoutTop = getLayoutTop(child);
                info.layoutLeft = getLayoutLeft(child);
                info.layoutRect = child.getComponentPosition();
                if (info.layoutRect == null || (info.layoutRect.left == 0 && info.layoutRect.right == 0
                        && info.layoutRect.top == 0 && info.layoutRect.bottom == 0)) {
                    continue;
                }
                if (fromTop) {
                    info.targetTop = component == child;
                } else {
                    info.targetLeft = component == child;
                }

                container.put(child.hashCode(), info);
            } else {
                boolean target = component == child;
                if (fromTop) {
                    info.targetTop = target;
                } else {
                    info.targetLeft = target;
                }
            }
        }
    }

    static class ComponentInfo {
        public int layoutTop;
        public int layoutLeft;
        public Rect layoutRect;
        public boolean targetTop;
        public boolean targetLeft;
        public int offsetTop;
        public int offsetLeft;
        public int otherOffsetTop;
        public int otherOffsetLeft;

        @Override
        public String toString() {
            return "{" +
                    "offsetTop=" + offsetTop +
                    ", offsetLeft=" + offsetLeft +
                    ", targetTop=" + targetTop +
                    ", targetLeft=" + targetLeft +
                    ", layoutTop=" + layoutTop +
                    ", layoutLeft=" + layoutLeft +
                    '}';
        }
    }
}
